讓我們來看一下昨天最後一個使用到prototype的範例:
const contestant = {
contestantId: 1,
contestantName: "Alice",
}
const addNewItem = { fruit:'apple',};
Object.setPrototypeOf(contestant,addNewItem);
//新增原型之後
console.log(contestant);
//印出:
//{
// contestantId: 1,
// contestantName: 'Alice',
for(const key in contestant){
console.log(key);
//印出:
// contestantId
// contestantName
// fruit
}
還記得for-in
會連同原型一起被印出嗎?請觀察兩者之間的差異,是不是發現如果單獨印出contestant
的值,並沒有fruit
這個屬性,就好像是被隱形起來的內容。而今天就是要講一下prototype(原型)在做些什麽。
先來看看MDN在開頭如何說明:
Prototypes are the mechanism by which JavaScript objects inherit features from one another.
原型是JavaScript相互繼承功能的機制。
光是上面的話還是很難理解,來使用devtool創立一個物件,點開來看一下有什麽內容:
在上面創立一個物件之後,當我使用物件,從內容點開來就會發現有個[[Prototype]]
能被點開來,其內容可由大家自行查閱。但這裡想提的是,object本身無論有沒有特別設置原型,在他的機制中原本就會預設一個給物件使用。
再來,原型本身就是一個物件,這個原型還會有自己的原型,直到找到null
為止。比方我們以上面創建的object,使用.__proto__
訪問器(accessor)來找他的原型:
會發現我們第一次用.__proto__
找到object的原型,再往上找就是空的。
接著來看一下物件繼承是什麽樣子。
讓我們在範例中為原有物件加上原型:
const eligibility = {
adult:true,
}
const contestant = {
contestantId: 1,
contestantName: "Alice",
__proto__:eligibility
}
console.log(contestant.adult)//true
eligibility
是contestant
的原型,而contestant
繼承eligibility
的屬性與方法,也就是說透過eligibility
這個物件增加屬性與方法,contestant
也能使用繼承過來的內容。
感受一下不同物件繼承同樣的原型:
const contestant1 = {
contestantId: 1,
contestantName: "Alice",
__proto__:eligibility
}
const contestant2 = {
contestantId: 2,
contestantName: "Bob",
__proto__:eligibility
}
const contestant3 = {
contestantId: 3,
contestantName: "Charlie",
__proto__:eligibility
}
console.log(contestant1.adult);//true
console.log(contestant2.adult);//true
console.log(contestant3.adult);//true
我們在剛才為contestant
物件設置eligibility
的原型,那如果eligibility
也有原型,來看看會怎樣:
const item = {
upper:1,
}
const eligibility = {
adult:true,
__proto__:item,
}
const contestant = {
contestantId: 1,
contestantName: "Alice",
__proto__:eligibility
}
console.log(contestant.upper);//1
console.log(contestant.adult);//true
console.log(contestant.__proto__.__proto__);//{ upper: 1 }
可以發現,原型不僅能夠增加自己的原型,也能讓新繼承的物件使用原型所繼承的屬性或方法,一樣能透過.__proto
與一些方法查找原型,這樣一個個連結的關係讓這些原型產生原型鍊(prototype chain)。
我們可以使用Object.create()
在一開始就設置物件的原型。
const eligibility = {
adult:true,
}
const newContestant = Object.create(eligibility)
console.log(newContestant.adult)//true
雖然上面使用.__proto__
範例找原型的過程,但實際上只剩瀏覽器支持此用法,不推薦使用,如果想要找到object的原型,可以使用Object.getPrototypeOf()
或Reflect.getPrototypeOf()
const contestant = {
contestantId: 1,
contestantName: "Alice",
hotpotFlavor: "Spicy Sichuan",
hotpotIngredients: ["Beef slices", "Tofu", "Enoki mushrooms", "Napa cabbage"],
summarizeCooking: function () {
return "Balancing spicy Sichuan flavor with tender beef, tofu, and crunchy cabbage. Pairing with fragrant jasmine tea enhances the experience.";
},
}
const addNewItem = { fruit:'apple',};
Object.setPrototypeOf(contestant,addNewItem);
//新增原型之後,透過上述兩個方式取得原型物件
console.log(Object.getPrototypeOf(contestant));//{ fruit: 'apple' }
console.log(Reflect.getPrototypeOf(contestant));//{ fruit: 'apple' }
雖然在最一開始已經使用過Object.setPrototypeOf()
的使用,但讓我們再一次不透過.__proto__
來範例。
const eligibility = {
adult:true,
__proto__:item,
}
const contestant = {
contestantId: 1,
contestantName: "Alice",
__proto__:eligibility
}
Object.setPrototypeOf(contestant,eligibility)
console.log(Object.getPrototypeOf(contestant));
上面有提到檢視物件時可以用Reflect.getPrototypeOf()
看到原型的內容,但Reflect.setPrototypeOf()
不一樣的是,在使用Reflect.setPrototypeOf()
設置原型時,他會回傳一個boolean值。
console.log(Object.setPrototypeOf(contestant,eligibility));
//回傳物件本身:{ contestantId: 1, contestantName: 'Alice' }
console.log(Reflect.setPrototypeOf(contestant,eligibility));
//回傳物件成功與否:true
Day15-Object.create() 介紹
15. [JS] 什麼是原型鏈?
How can I read ‘native code’ JavaScript functions?
書
忍者:JavaScript開發技巧探秘第二版(第七章)
MDN